package com.gitee.feizns.dynamic.bean;

import com.gitee.feizns.dynamic.reflect.Lambdas;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

/**
 * <b>默认的属性元信息实现器</b>
 * @author feizns
 * @since 2019/6/9 0009
 */
class DefaultPropImpl<D, S> implements Prop<D, S> {

    /**
     * 字段
     */
    private Field field;

    /**
     * 原始对象类型
     */
    private Class<S> sourceClass;

    /**
     * 原始对象
     */
    private S source;

    /**
     * 读方法
     */
    private Method readMethod;

    /**
     * 写方法
     */
    private Method writeMethod;

    /**
     * 属性名称
     */
    private String name;

    /**
     * 默认属性impl
     */
    private DefaultPropImpl() {}

    /**
     * <b>使用对象和属性名称来构建一个属性/b>
     * @param source 属性所属对象
     * @param propertyName 属性名称
     */
    public DefaultPropImpl(S source, String propertyName) {
        this(source, propertyName, null);
    }

    /**
     * <b>使用对象和getter方法引用来构建一个属性/b>
     * <pre>{@code
     *  Prop<String, User> p = new DefaultPropImpl(new User(), User::getName);
     * }</pre>
     * @param source 属性所属对象
     * @param getter GET方法引用
     */
    public DefaultPropImpl(S source, Getter<S, D> getter) {
        this(source, Props.name(Lambdas.ofImplMethodName(getter)), null);
    }

    /**
     * <b>使用对象和属性名称来构建一个属性/b>
     * @apiNote @apiNote 如果属性是一个只写属性（也就不会有field和readMethod），则需要通过propertyClass指名writeMethod的参数类型
     * @param source 原对象
     * @param propertyName 属性名称
     * @param propertyClass 属性类型（默认取field,和readMethod的类型来作为属性类型，没有则取参数给定的类型）
     */
    @SuppressWarnings("unchecked")
    public DefaultPropImpl(S source, String propertyName, Class<?> propertyClass) {
        init((Class<S>) source.getClass(), propertyName, propertyClass);
        this.source = source;
    }

    /**
     * <b>使用目标类型，属性名称，来初始化属性元信息</b>
     * @param sourceClass 目标类型
     * @param propertyName 属性名称
     */
    public DefaultPropImpl(Class<S> sourceClass, String propertyName) {
        this(sourceClass, propertyName, null);
    }

    /**
     * <b>使用目标类型，getter方法引用，来初始化属性元信息</b>
     * @param sourceClass 目标类型
     * @param getter getter方法引用
     */
    public DefaultPropImpl(Class<S> sourceClass, Getter<S, D> getter) {
        this(sourceClass, Props.name(Lambdas.ofImplMethodName(getter)), null);
    }

    /**
     * <b>使用目标类型，属性名称，属性类型来初始化属性元信息</b>
     * @apiNote 如果属性是一个只写属性（也就不会有field和readMethod），则需要通过propertyClass指名writeMethod的参数类型
     * @param sourceClass 目标类型
     * @param propertyName 属性名称
     * @param propertyType 属性类型
     */
    public DefaultPropImpl(Class<S> sourceClass, String propertyName, Class<?> propertyType) {
        init(sourceClass, propertyName, propertyType);
    }

    /**
     * 初始化元信息
     * @apiNote 如果属性是一个只写属性（也就不会有field和readMethod），则需要通过propertyClass指名writeMethod的参数类型
     * @param targetClass 属性所属的类型
     * @param propName 属性名称
     * @param propClass 属性类型
     */
    public void init(Class<S> targetClass, String propName, Class<?> propClass) {
        this.name = propName;
        if ( targetClass != null ) {
            this.sourceClass = targetClass;
            this.readMethod = Props.getReadMethod(targetClass, propName);
            this.field = Props.getField(targetClass, propName);
            //先查找field，readMethod上的类型，如果不存在field和readMethod则要保证传入了propClass才能查找到对应的writeMethod
            Class<?> propClz = getPropClass();
            propClass = propClz != null ? propClz : propClass;
            if ( propClass != null )
                this.writeMethod = Props.getWriteMethod(targetClass, propName, propClass);
        }
    }

    @Override
    public Prop<D, S> of(S source) {
        DefaultPropImpl<D, S> newProp = new DefaultPropImpl<>();
        newProp.name = this.name;
        newProp.sourceClass = this.sourceClass;
        newProp.readMethod = this.readMethod;
        newProp.field = this.field;
        newProp.writeMethod = this.writeMethod;
        newProp.source = source;
        return newProp;
    }


    /**
     * <b>获取属性类型</b>
     * @return 从字段类型和读方法上获取对应属性的类型
     */
    private Class<?> getPropClass() {
        return field != null ? field.getType() : (readMethod != null ? readMethod.getReturnType() : writeMethodParameterType());
    }

    @Override
    public Class<S> getSourceClass() {
        return sourceClass;
    }

    @Override
    public String name() {
        return name;
    }

    @Override
    public S getSource() {
        return source;
    }

    @Override
    public Field field() {
        return field;
    }

    @Override
    public Method readMethod() {
        return readMethod;
    }

    @Override
    public Method writeMethod() {
        return writeMethod;
    }

    @Override
    public String toString() {
        return "Prop [ name = " + name + ", " +
                "field = " + field + ", " +
                "target = " + source + ", " +
                "targetClass = " + sourceClass + ", " +
                "readMethod = " + readMethod + ", " +
                "writeMethod = " + writeMethod + " ]";
    }

}
